﻿/// <reference path="../Core.ts" />
/// <reference path="../Libraries/jasmine/jasmine.d.ts" />

interface Window {
    Three: any;
}

describe('ExtSpec', function () {
    'use strict';

    describe('typeOfObject', function () {
        it('should return "Number" given NaN', function () {
            expect(ExtSpec.typeOfObject(NaN)).toBe('Number');
        });

        it('should return "RegExp" given /[a-z]/', function () {
            expect(ExtSpec.typeOfObject(/[a-z]/)).toBe('RegExp');
        });

        it('should return "Null" given null', function () {
            expect(ExtSpec.typeOfObject(null)).toBe('Null');
        });
    });

    describe('typeOf', function () {
        it('should return "null" given null', function () {
            expect(ExtSpec.typeOf(null)).toBe('null');
        });

        it('should return "nan" given NaN', function () {
            expect(ExtSpec.typeOf(NaN)).toBe('nan');
        });

        it('should return "infinity" given Infinity', function () {
            expect(ExtSpec.typeOf(Infinity)).toBe('infinity');
        });

        it('should call typeOfObject given an object type and return its lowercased result', function () {
            spyOn(ExtSpec, 'typeOfObject').and.returnValue('ObjectType');

            var value = [],
                actual = ExtSpec.typeOf(value);

            expect(ExtSpec.typeOfObject).toHaveBeenCalledWith(value);
            expect(actual).toBe('objecttype');
        });

        it('should return "undefined" given undefined', function () {
            expect(ExtSpec.typeOf(undefined)).toBe('undefined');
        });
    });

    describe('isTypeOf', function () {
        it('should call typeOf passing through the value argument', function () {
            spyOn(ExtSpec, 'typeOf').and.callThrough();

            ExtSpec.isTypeOf('my value', undefined);

            expect(ExtSpec.typeOf).toHaveBeenCalledWith('my value');
        });

        it('should compare the result of typeOf with the compare argument', function () {
            spyOn(ExtSpec, 'typeOf').and.returnValue('mytype');

            var actual = ExtSpec.isTypeOf('my value', 'mytype');

            expect(actual).toBeTruthy();
        });

        it('should return true given "my value" and "st"', function () {
            expect(ExtSpec.isTypeOf('my value', 'st')).toBeTruthy();
        });

        it('should return true given -102.3 and "nm"', function () {
            expect(ExtSpec.isTypeOf(-102.3, 'nm')).toBeTruthy();
        });

        it('should return false given undefined and "nl"', function () {
            expect(ExtSpec.isTypeOf(undefined, 'nl')).toBeFalsy();
        });

        it('should return false given null and "un"', function () {
            expect(ExtSpec.isTypeOf(null, 'un')).toBeFalsy();
        });

        it('should return true given [] and "ar"', function () {
            expect(ExtSpec.isTypeOf([], 'ar')).toBeTruthy();
        });

        it('should return true given new String() and "st"', function () {
            expect(ExtSpec.isTypeOf(new String(), 'st')).toBeTruthy(); //ignore jslint
        });
    });

    describe('isObjectType', function () {
        it('should return true given {}', function () {
            expect(ExtSpec.isObjectType({})).toBeTruthy();
        });

        it('should return true given []', function () {
            expect(ExtSpec.isObjectType([])).toBeTruthy();
        });

        it('should return true given /[a-z]/', function () {
            expect(ExtSpec.isObjectType(/[a-z]/)).toBeTruthy();
        });

        it('should return true given function () { }', function () {
            expect(ExtSpec.isObjectType(function () { })).toBeTruthy();
        });

        it('should return true given new MyConstructor()', function () {
            function MyConstructor() { }

            expect(ExtSpec.isObjectType(new MyConstructor())).toBeTruthy();
        });

        it('should return true given new String()', function () {
            expect(ExtSpec.isObjectType(new String())).toBeTruthy(); //ignore jslint
        });

        it('should return false given "abcd"', function () {
            expect(ExtSpec.isObjectType('abcd')).toBeFalsy();
        });

        it('should return false given 123.456', function () {
            expect(ExtSpec.isObjectType(123.456)).toBeFalsy();
        });

        it('should return false given false', function () {
            expect(ExtSpec.isObjectType(false)).toBeFalsy();
        });
    });

    describe('objectKeysMatch', function () {
        var value = {
                key1: true,
                key2: 'string',
                key3: function () { },
                key4: 12345,
                key5: {
                    subKeyA: 'A',
                    subKeyB: 'B'
                }
            };

        it('should return true given one matching key with a primitive value', function () {
            expect(ExtSpec.objectKeysMatch(value, {
                key1: true
            })).toBeTruthy();
        });

        it('should return true given three matching keys with primitive values', function () {
            expect(ExtSpec.objectKeysMatch(value, {
                key1: true,
                key2: 'string',
                key4: 12345
            })).toBeTruthy();
        });

        it('should return false given one matching key with object value', function () {
            expect(ExtSpec.objectKeysMatch(value, {
                key5: {
                    subKeyA: 'A',
                    subKeyB: 'B'
                }
            })).toBeFalsy();
        });
    });

    describe('apply', function () {
        it('should throw given source without destination', function () {
            var destination,
                source = {
                    key: true
                };

            expect(function () {
                ExtSpec.apply(destination, source);
            }).toThrowError('The destination argument must be an object type.');
        });

        it('should throw given destination without source', function () {
            var destination = {},
                source;

            expect(function () {
                ExtSpec.apply(destination, source);
            }).toThrowError('The source argument must be an object type.');
        });

        it('should copy new properties to the destination from the source', function () {
            var destination: any = {},
                source = {
                    newKey1: 'value1',
                    newKey2: 'value2'
                };

            ExtSpec.apply(destination, source);

            expect(destination.newKey1).toBe('value1');
            expect(destination.newKey2).toBe('value2');
        });

        it('should overwrite an existing property in the destination from the source', function () {
            var destination = {
                    existingKey: 'old value'
                },
                source = {
                    existingKey: 'new value'
                };

            ExtSpec.apply(destination, source);

            expect(destination.existingKey).toBe('new value');
        });

        it('should not copy skipped properties to destination', function () {
            var destination: any = {},
                source = {
                    statics: {},
                    inheritableStatics: {},
                    otherProperty: 'copied'
                };

            ExtSpec.apply(destination, source, ['statics', 'inheritableStatics']);

            expect(destination.otherProperty).toBe('copied');
            expect(destination.statics).toBeUndefined();
            expect(destination.inheritableStatics).toBeUndefined();
        });
    });

    describe('ensureNamespace', function () {
        afterEach(function () {
            delete window.Three;
        });

        it('should throw given an empty or undefined namespace', function () {
            expect(function () {
                ExtSpec.ensureNamespace('');
            }).toThrowError('The namespace parameter must be a non empty string.');

            expect(function () {
                ExtSpec.ensureNamespace(undefined);
            }).toThrowError('The namespace parameter must be a non empty string.');
        });

        it('should throw given a primitive root', function () {
            expect(function () {
                ExtSpec.ensureNamespace('Name.space', 1235);
            }).toThrowError('The root parameter must be of an object type if supplied.');
        });

        it('should create a three level deep namespace on the global object given "Three.levels.Deep"', function () {
            ExtSpec.ensureNamespace('Three.levels.Deep');

            expect(window.Three.levels.Deep).toBeDefined();
        });

        it('should convert a primitive to an object if it collides with a namespace', function () {
            window.Three = {
                levels: 'Collision!'
            };

            expect(typeof window.Three.levels).toBe('string');

            ExtSpec.ensureNamespace('Three.levels.Deep');

            expect(typeof window.Three.levels).toBe('object');
            expect(window.Three.levels.toString()).toBe('Collision!');
            expect(Object.prototype.toString.call(window.Three.levels)).toBe('[object String]');
            expect(window.Three.levels.Deep).toBeDefined();
        });

        it('should replace null with an object if it collides with a namespace', function () {
            window.Three = {
                levels: null
            };

            expect(window.Three.levels).toBeNull();

            ExtSpec.ensureNamespace('Three.levels.Deep');

            expect(typeof window.Three.levels).toBe('object');
            expect(window.Three.levels).toBeDefined();
            expect(window.Three.levels.Deep).toBeDefined();
        });

        it('should attach the namespace to a custom root object if provided', function () {
            var root: any = function () {};

            ExtSpec.ensureNamespace('Three.levels.Deep', root);

            expect(root.Three.levels.Deep).toBeDefined();
        });

        it('should return the last part of the namespace', function () {
            var actual,
                last = [];

            window.Three = {
                levels: {
                    Deep: last
                }
            };

            actual = ExtSpec.ensureNamespace('Three.levels.Deep');

            expect(actual).toBe(last);
        });
    });

    describe('getResourceConfig', function () {
        it('should throw given no arguments', function () {
            expect(function () {
                ExtSpec.getResourceConfig(undefined);
            }).toThrowError('Unsupported type "undefined".');
        });

        it('should throw given an unsupported type', function () {
            expect(function () {
                ExtSpec.getResourceConfig('store');
            }).toThrowError('Unsupported type "store".');
        });

        it('should return model config given "md"', function () {
            var actual = ExtSpec.getResourceConfig('md');

            expect(actual.suffix).toBe('Model');
        });

        it('should return view config given "vw"', function () {
            var actual = ExtSpec.getResourceConfig('vw');

            expect(actual.suffix).toBe('View');
        });

        it('should return controller config given "ct"', function () {
            var actual = ExtSpec.getResourceConfig('ct');

            expect(actual.suffix).toBe('Controller');
        });

        it('should return store config given "st"', function () {
            var actual = ExtSpec.getResourceConfig('st');

            expect(actual.suffix).toBe('Store');
        });

        it('should return ref config given "rf"', function () {
            var actual = ExtSpec.getResourceConfig('rf');

            expect(actual.suffix).toBeUndefined();
            expect(actual.property).toBe('refs');
            expect(actual.itemProperty).toBe('ref');
        });

        it('should return require config given "rq"', function () {
            var actual = ExtSpec.getResourceConfig('rq');

            expect(actual.suffix).toBeUndefined();
            expect(actual.property).toBe('requires');
        });

        it('should return configuration config given "cf"', function () {
            var actual = ExtSpec.getResourceConfig('cf');

            expect(actual.suffix).toBeUndefined();
            expect(actual.property).toBe('config');
        });
    });

    describe('getTplConfig', function () {
        it('should throw given no arguments', function () {
            expect(function () {
                ExtSpec.getTplConfig(undefined);
            }).toThrowError('The instance argument is undefined or not an object.');
        });

        it('should throw given an object without tpl property', function () {
            expect(function () {
                ExtSpec.getTplConfig({});
            }).toThrowError('The "tpl" property must be an array.');
        });

        it('should throw given an object with a non Array tpl property', function () {
            expect(function () {
                ExtSpec.getTplConfig({
                    tpl: false
                });
            }).toThrowError('The "tpl" property must be an array.');
        });

        it('should throw given a tpl without a configuration element', function () {
            expect(function () {
                ExtSpec.getTplConfig({
                    tpl: [
                        'First',
                        'Second',
                        'Third'
                    ]
                });
            }).toThrowError('The last element of the "tpl" array is not a configuration object.');
        });

        it('should return the configuration element of a tpl', function () {
            var config = {},
                actual = ExtSpec.getTplConfig({
                    tpl: [
                        'First',
                        'Second',
                        'Third',
                        config
                    ]
                });

            expect(actual).toBe(config);
        });
    });

    describe('warn', function () {
        it('should prefix warning message with "Ext Spec Warning:"', function () {
            spyOn(ExtSpec.ConsoleWrapper, 'warn');

            ExtSpec.warn('warning');
            expect(ExtSpec.ConsoleWrapper.warn).toHaveBeenCalledWith('Ext Spec Warning: warning');
        });
    });

    describe('obsolete', function () {
        beforeEach(function () {
            spyOn(ExtSpec.ConsoleWrapper, 'warn');
        });

        it('should output to the console given only a method name', function () {
            ExtSpec.obsolete('something');

            expect(ExtSpec.ConsoleWrapper.warn).toHaveBeenCalledWith('"something" is obsolete.');
        });

        it('should output to the console given method and namespace', function () {
            ExtSpec.obsolete({
                method: 'something',
                namespace: 'Name.space'
            });

            expect(ExtSpec.ConsoleWrapper.warn).toHaveBeenCalledWith('"Name.space.something" is obsolete.');
        });

        it('should output to the console given method, namespace and message', function () {
            ExtSpec.obsolete({
                method: 'something',
                namespace: 'Name.space',
                message: 'Use alternative.'
            });

            expect(ExtSpec.ConsoleWrapper.warn).toHaveBeenCalledWith('"Name.space.something" is obsolete: Use alternative.');
        });

        it('should output to the console given method and message', function () {
            ExtSpec.obsolete({
                method: 'something',
                message: 'Use alternative.'
            });

            expect(ExtSpec.ConsoleWrapper.warn).toHaveBeenCalledWith('"something" is obsolete: Use alternative.');
        });
    });
});